/* sm_core.c -- Arquivo central
 *
 * Copyright (C) 2008, Diogo F. S. Ramos <diogo.ramos@usp.br>
 *                     Márcio Rogério
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <GL/glut.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>

#include "sm_core.h"
#include "sm_boat.h"
#include "sm_utils.h"

/* Defines */
#define FRAME_RATE  45
#define TEMPO_FRAME 1000/FRAME_RATE

/* Variáveis privadas */
static int PAUSE;
static sm_world_t * world;

/* Static Functions */
static void sm_init_glut(int argc, char *argv[]);
static void sm_display(void);
static void sm_reshape(const int width, const int height);
static void sm_keyboard(const unsigned char tecla, const int x, const int y);
static void sm_timer( const int data );
static void sm_init(void);
static void sm_mouse( int x, int y );
static void sm_core_camera_rotateY( const float angle );
static void sm_core_camera_move( const int direction );
static void sm_core_camera_update_look( void );
static void sm_core_perspective_view(int x, int y);
static void sm_core_set_hud_mode( const int flag );
static void sm_core_hud_draw(void );

void
sm_run( int argc, char *argv[])
{
    sm_init_glut( argc, argv);
    sm_init();

    /* Entra no looping principal */
    glutMainLoop();
}

static void
sm_init()
{
    /* Variáveis */
    PAUSE = 0;
    PAUSE = ~PAUSE;
    
    /* Setando a iluminação */
    GLfloat luz_ambiente[]= { 0.0, 0.0, 0.0, 1.0 };
    GLfloat luz_difusa[]= { 1.0, 1.0, 1.0, 1.0 };
    GLfloat luz_especular[]= { 0.0, 0.0, 0.0, 1.0 };
    GLfloat luz_posicao[]= { 0.0, 30.0, 30.0, 0.0 };
    glLightfv(GL_LIGHT0, GL_POSITION, luz_posicao);
    glLightfv(GL_LIGHT0, GL_AMBIENT, luz_ambiente);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, luz_difusa);
    glLightfv(GL_LIGHT0, GL_SPECULAR, luz_especular);

    glShadeModel(GL_SMOOTH);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);
    
    /* Define a cor de fundo da janela de visualização como preta */
    glClearColor( 0.18f, 0.2823f, 0.6862f, 1.0f);

    /* Iniciando objetos */
    world = XMALLOC( sm_world_t, 1 );

    world->boat = sm_boat_new();
    world->sea = sm_sea_new(30, 30);

    world->wire_frame = 0;
    world->hud_front = 0;

    /* Camera */
    world->camera = XMALLOC( sm_camera_t, 1 );
    world->camera->mouse_posX_ant = 0.0;
    world->camera->mouse_posY_ant = 0.0;
    world->camera->angleX = 0.0;
    world->camera->angleY = 0.0;
    world->camera->angle_inc = M_PI/180.0;
    world->camera->screen_width = 860;
    world->camera->screen_height = 480;

    world->camera->look_at_X = 0.0;
    world->camera->look_at_Y = 0.0;
    world->camera->look_at_Z = 0.0;

    world->camera->line_of_sightX = 0.0;
    world->camera->line_of_sightY = 0.0;
    world->camera->line_of_sightZ = -1.0;
    
    world->camera->posX = 5.0;
    world->camera->posY = 0.0;
    world->camera->posZ = 20.0;
    world->camera->look_at_radius = 100;

    sm_core_camera_update_look();
    
    
    glLoadIdentity();

    return;
}

static void
sm_init_glut(int argc, char *argv[] )
{
    /* Inicia o GLUT */
    glutInit(&argc, argv);
    /* Inicia que tipo de janela vai ser utilizada */
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);
    /* Que tamanho? 860x480 esta bom... */
    glutInitWindowSize(860,480);
    /* Que posição, hein? */
    glutInitWindowPosition(10,10);
    /* Criando a janela */
    glutCreateWindow("Santa Maria");
    /* Definindo qual função vai ser utilizada para redesenhar a janela */
    glutDisplayFunc( sm_display );
    /* Como redesenhar a cena? Eu te conto! */
    glutReshapeFunc( sm_reshape );
    /* Callback para o teclado */
    glutKeyboardFunc( sm_keyboard );
    /* Função de animação */
    glutTimerFunc( TEMPO_FRAME, sm_timer, 0 );
    /* Movimentos do mouse */
    glutMotionFunc( sm_mouse );
}

static void
sm_mouse( int x, int y )
{
    if( x > world->camera->mouse_posX_ant )
    	world->camera->angleX += world->camera->angle_inc;
    else if( x < world->camera->mouse_posX_ant )
    	world->camera->angleX -= world->camera->angle_inc;
    world->camera->mouse_posX_ant = x;

    if( y > world->camera->mouse_posY_ant )
    	world->camera->angleY += 0.5;
    else if( y < world->camera->mouse_posY_ant )
    	world->camera->angleY -= 0.5;    
    world->camera->mouse_posY_ant = y;

    world->camera->posY = world->camera->angleY;

    sm_core_camera_rotateY( world->camera->angleX );

}

static void
sm_display(void)
{
    glMatrixMode(GL_MODELVIEW);
    /* Limpa a janela de visualização com a cor de fundo especificada */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    /* HUD */
    if( world->hud_front == 0)
	sm_core_hud_draw();
    
    /* Transformações da câmera */
    gluLookAt( world->camera->posX,
	       world->camera->posY,
	       world->camera->posZ,
	       
	       world->camera->look_at_X,
	       world->camera->look_at_Y,
	       world->camera->look_at_Z,
	       
	       0.0, 1.0, 0.0 );

    if( world->wire_frame != 0 )
	glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
    else
	glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
    
    /* Barco */
    glPushMatrix();{
	world->boat->draw((void*)world->boat);	
    }glPopMatrix();

    /* HUD */
    if( world->hud_front != 0)
	sm_core_hud_draw();

    glutSwapBuffers();
}

static void
sm_core_hud_draw(void )
{
    GLfloat hud_ambiente[]= { 1.0, 1.0, 1.0, 1.0 };
    GLfloat hud_difusa[]= { 0.0, 1.0, 0.0, 1.0 };
    GLfloat hud_especular[]= { 1.0, 1.0, 1.0, 1.0 };
    GLfloat hud_brilho = 50.0;

    hud_difusa[0] = 1.8-sm_boat_motor_turbo_get(world->boat);
    hud_difusa[1] = sm_boat_motor_turbo_get(world->boat)-0.9;

    glPushMatrix();{
	sm_core_set_hud_mode(1);
	glMaterialfv(GL_FRONT, GL_SPECULAR, hud_especular);
	glMaterialfv(GL_FRONT, GL_AMBIENT, hud_ambiente);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, hud_difusa);
	glMaterialf(GL_FRONT, GL_SHININESS, hud_brilho);
	glBegin( GL_QUADS );{
	    glVertex2i(0, 0);
	    glVertex2i(0, 10 *sm_boat_motor_get_speed(world->boat));
	    glVertex2i(50,
		       10*sm_boat_motor_get_speed(world->boat));
	    glVertex2i(50, 0);
	}glEnd();
	sm_core_set_hud_mode(0);
    }glPopMatrix();
}

static void
sm_core_perspective_view( int width, int heighty )
{
    int height = heighty;
    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glViewport(0, 0, width, height);

    /* Perspectiva 3D */
    gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,1000.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt( world->camera->posX,
	       world->camera->posY,
	       world->camera->posZ,
	       
	       world->camera->look_at_X,
	       world->camera->look_at_Y,
	       world->camera->look_at_Z,
	       
	       0.0, 1.0, 0.0 );
    
    world->camera->screen_width = width;
    world->camera->screen_height = heighty;
}

static void
sm_core_set_hud_mode( const int flag )
{
    if(flag)
    {
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();

	gluOrtho2D(0, world->camera->screen_width,
		   0, world->camera->screen_height);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	glDisable(GL_DEPTH_TEST);
    }
    else
    {
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
	glEnable(GL_DEPTH_TEST);
    }

}

static void
sm_reshape( const int width, const int heighty )
{
    int height = heighty;
    
    if( height == 0 )
	height = 1;
    sm_core_perspective_view( width , height );
}

static void
sm_core_camera_rotateY( const float angle )
{
    world->camera->line_of_sightX = sin( angle );
    world->camera->line_of_sightZ = -cos( angle );

    sm_core_camera_update_look( );
}

static void
sm_core_camera_update_look( void )
{
    world->camera->look_at_X = world->camera->posX +	
	world->camera->line_of_sightX;
    world->camera->look_at_Y = world->camera->posY +	
	world->camera->line_of_sightY;
    world->camera->look_at_Z = world->camera->posZ +
	world->camera->line_of_sightZ;
}

static void
sm_core_camera_move( const int direction )
{
    world->camera->posX = world->camera->posX + direction *
	world->camera->line_of_sightX*0.5;
    world->camera->posY = world->camera->posY + direction *
	world->camera->line_of_sightY*0.5;
    world->camera->posZ = world->camera->posZ + direction *
	world->camera->line_of_sightZ*0.5;

    sm_core_camera_update_look( );
}

static void
sm_keyboard(const unsigned char key, const int x, const int y)
{
    switch( key )
    {
    case 27:
    {
	exit( EXIT_SUCCESS );
	break;
    }
    case 'h':
    {
	world->hud_front = ~world->hud_front;
	break;
    }
    case '+':
    {
	sm_boat_motor_turbo_increase(world->boat);
	break;
    }
    case '-':
    {
	sm_boat_motor_turbo_decrease(world->boat);
	break;
    }
    case 'W':
    {
	sm_boat_sail_forward( world->boat );
	break;
    }
    case 'c':
    {
	sm_boat_toggle_anchor( world->boat );
	break;
    }
    case 'w':
    {
	sm_core_camera_move(1);
	break;
    }
    case '0':
    {
	sm_boat_motor_set_speed( world->boat, 0 );
	break;
    }
    case '1':
    {
	sm_boat_motor_set_speed( world->boat, 1 );
	break;
    }
    case '2':
    {
	sm_boat_motor_set_speed( world->boat, 2 );
	break;
    }
    case '3':
    {
	sm_boat_motor_set_speed( world->boat, 3 );
	break;
    }
    case 'o':
    {
	sm_boat_keel_move( world->boat, 1 );
	break;
    }
    case 'i':
    {
	sm_boat_keel_move( world->boat, -1 );
	break;
    }
    case 'r':
    {
	sm_boat_motor_toggle_reverse( world->boat );
	break;
    }
    case 'k':
    {
	sm_boat_cargo_door_move( world->boat, 1 );
	break;
    }
    case 'l':
    {
	sm_boat_cargo_door_move( world->boat, -1 );
	break;
    }
    case 's':
    {
	sm_core_camera_move(-1);
	break;
    }
    case 'a':
    {
	sm_boat_turn_left( world->boat );
	break;
    }
    case 'd':
    {
	sm_boat_turn_right( world->boat );
	break;
    }
    case 'f':
    {
	world->wire_frame = ~(world->wire_frame);
    }
    case 'p':
    {
	PAUSE = ~PAUSE;
	break;
    }
    }
}

static void
sm_timer( const int data )
{
    if( !PAUSE )
    {
	sm_boat_motor_turn(world->boat);
    }

    glutPostRedisplay();
    glutTimerFunc( TEMPO_FRAME, sm_timer, 0 );
    
    return;
}
